using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Surfaces;

namespace Beaker.OpenCube.Render.Shapes
{
	public sealed class GameCube
		: IShape
	{
		private readonly ISurface _surface;
		private readonly Point3 _center;
		private readonly Point3 _corner1;
		private readonly Point3 _corner2;

		public GameCube( Point3 center, ISurface surface )
		{
			_surface = surface;
			_center = center;
			_corner1 = _center - new Vector3( 0.5d, 0.5d, 0.5d );
			_corner2 = _center + new Vector3( 0.5d, 0.5d, 0.5d );
		}

		/// <summary>
		/// Computes the intersection with the given ray.
		/// </summary>
		/// <param name="ray">The ray to compute an intersection with.</param>
		/// <returns>
		/// An intersection or <c>null</c> when there was no intersection.
		/// </returns>
		[Pure]
		public Intersection ComputeIntersection( Ray ray )
		{
			double tNear = double.NegativeInfinity;
			double tFar = double.PositiveInfinity;
			
			var rd = ray.Direction;
			var ro = ray.Origin;

			double rdx = rd.X;
			double rox = ro.X;
			double x1 = _corner1.X;
			double x2 = _corner2.X;

			if( rdx.AboutZero() && rox < x1 && rox > x2 )
				return null;
			else
			{
				double t1 = ( x1 - rox ) / rdx;
				double t2 = ( x2 - rox ) / rdx;

				Helpers.Order( ref t1, ref t2 );
				tNear = Math.Max( tNear, t1 );
				tFar = Math.Min( tFar, t2 );

				if( ( tNear > tFar ) || ( tFar < 0d ) )
					return null;
			}

			double rdy = rd.Y;
			double roy = ro.Y;
			double y1 = _corner1.Y;
			double y2 = _corner2.Y;

			if( rdy.AboutZero() && roy < y1 && roy > y2 )
				return null;
			else
			{
				double t1 = ( y1 - roy ) / rdy;
				double t2 = ( y2 - roy ) / rdy;

				Helpers.Order( ref t1, ref t2 );
				tNear = Math.Max( tNear, t1 );
				tFar = Math.Min( tFar, t2 );

				if( ( tNear > tFar ) || ( tFar < 0d ) )
					return null;
			}

			double rdz = rd.Z;
			double roz = ro.Z;
			double z1 = _corner1.Z;
			double z2 = _corner2.Z;

			if( rdz.AboutZero() && roz < z1 && roz > z2 )
				return null;
			else
			{
				double t1 = ( z1 - roz ) / rdz;
				double t2 = ( z2 - roz ) / rdz;

				Helpers.Order( ref t1, ref t2 );
				tNear = Math.Max( tNear, t1 );
				tFar = Math.Min( tFar, t2 );

				if( ( tNear > tFar ) || ( tFar < 0d ) )
					return null;
			}

			Point3 point = ray.Origin + ray.Direction * tNear;
			return new Intersection( this, ray, tNear, point, ComputeNormal( point ), _surface );
		}

		/// <summary>
		/// Computes the normal at the given intersection point
		/// </summary>
		/// <param name="point">The point of the intersection, where to get the normal for.</param>
		/// <returns></returns>
		[Pure]
		public Vector3 ComputeNormal( Point3 point )
		{
			return
				new Vector3(
					point.X.AboutEqual( _corner1.X ) ? -1d : point.X.AboutEqual( _corner2.X ) ? 1d : 0d,
					point.Y.AboutEqual( _corner1.Y ) ? -1d : point.Y.AboutEqual( _corner2.Y ) ? 1d : 0d,
					point.Z.AboutEqual( _corner1.Z ) ? -1d : point.Z.AboutEqual( _corner2.Z ) ? 1d : 0d
				);
		}

		/// <summary>
		/// Gets the surface of the cube
		/// </summary>
		[Pure]
		public ISurface Surface
		{
			
			get { return _surface; }
		}
	}
}